import { def, isObject, logger } from "../utils/index";
import { arrayMethods } from "./array";
import { Dep } from "./dep";

// 响应式数据
export function observe(data) {
	// 如果数据不是对象就直接return
	if (!isObject(data)) return;
	return new Observer(data); // 返回用于检测数据的对象
}

// 观察数据
class Observer {
	constructor(value) {
		// 存储依赖,数组变化要用到
		this.dep = new Dep();
		// 给每一个劫持的值加上__ob__并且指向当前对象
		// value.__ob__ = this; 直接加会无限递归
		def(value, "__ob__", this);
		// 劫持数组
		if (Array.isArray(value)) {
			// 重写会导致数组改变的一些方法
			value.__proto__ = arrayMethods;
			// 数组不会对索引进行劫持,只对添加、删除等操作进行劫持
			this.observerArray(value);
		} else {
			// 劫持对象
			this.walk(value);
		}
	}
	// 处理数组数据
	observerArray(data) {
		// 遍历数组中的每个对象,进行劫持
		data.forEach((item) => observe(item));
	}
	// 处理对象数据
	walk(data) {
		// 拿到对象的所有key
		const keys = Object.keys(data);
		// 遍历处理数据
		keys.forEach((key) => {
			// 获取当前属性对应的值
			const value = data[key];
			// 定义响应式数据
			defineReactive(data, key, value);
		});
	}
}

// 响应式数据
function defineReactive(data, key, value) {
	// 这里递归劫持当前的值,因为当前的值可能是对象
	// 这里返回的可能是数组也可能是对象,数组会返回Observe实例,对象返回undefined
	const childOb = observe(value);
	/**
	 * Object.defineProperty()的作用就是直接在一个对象上定义一个新属性
	 * 或者修改一个已经存在的属性
	 * data 需要定义属性的当前对象
	 * key 当前需要定义的属性名
	 * {} 属性描述符
	 */
	// 保存当前值到依赖
	let dep = new Dep();
	Object.defineProperty(data, key, {
		configurable: true,
		enumerable: true,
		get() {
			// 判断Dep上有没有watcher
			if (Dep.target) {
				// 保存当前的watcher
				dep.depend();
				// 收集数组的依赖
				if (childOb) {
					childOb.dep.depend();
					// 如果数组中还有数组,当前的value是数组
					if (Array.isArray(value)) {
						dependArray(value);
					}
				}
			}
			return value;
		},
		set(newValue) {
			// 值没有变就不用操作
			if (newValue === value) return;
			// 继续劫持新数据,新数据可能是对象
			observe(newValue);
			value = newValue;
			logger.info("检测到数据更新,开始重新渲染视图");
			// 值改变通知watcher更新视图,先修改值后再通知更新
			dep.notify();
		},
	});
}

// 处理数组
function dependArray(arr) {
	// 遍历数组的每个值
	arr.forEach((item) => {
		// 如果当前的值还是数组就递归处理
		if (Array.isArray(item)) {
			dependArray(item);
		}
		// 拿到当前响应式对象的实例的专门给数组用来收集依赖的dep来收集当前依赖
		item.__ob__ && item.__ob__.dep.depend();
	});
}
